(2024/04/06更新) 因應React在18後更新了許多不同的語法,更新後的教學之後將陸續放在 新的blog 中,歡迎讀者到該處閱讀,我依然會回覆這邊的提問
在Day1,我們寫了一份很爛的程式碼,現在要來優化它。
當程式碼規模變大,我們勢必要分檔才能讓程式碼更能共用、易讀。但是要怎麼分檔才會好呢? 長年下來,工程師們逐漸發現一些特定的架構劃分方式可以普遍套用在專案中,這些設計模式(Design Pattern)就被整理了下來。
而工廠模式就是的其中一種很著名Design Pattern。
同樣是給麵粉。細麵工廠只會生出細麵,刀削麵工廠只會生出刀削麵。
聽起來好像是廢話,但這就是工廠模式的意義:
「工廠只負責把正確的材料,加工成他會做的東西。」
工廠模式,指的是某一個程式模組只負責製造一種東西,不管使用者在哪裡製造這個東西、拿這個東西去做什麼,都不會影響這段code定義的「製造過程」。
如果你搜尋「工廠模式」,你會發現一般是希望多個相近類別的物件透過繼承一個interface來實作抽象化的相同行為來取得工廠的製造物。然而這是因為強型態語言中,我們如果不使用介面或多型,程式語言會沒有辦法主動知道一個未知類別是不是有共同的方法、屬性。而Javascript是弱型態語言,我們可以像這樣,在不知道A是誰的狀況下,直接呼叫參數物件的方法:
function handleUnknownClass( A ){
A.getDomItem();
}
這樣雖然方便,但相對應的風險是當A沒有getDomItem()
時,程式就會死掉。
在大部分的物件導向程式語言中,我們可以把有相同性質的程式碼整理在一起,並把這一包模組稱為class
。
但Javascript在ES5之前,是不存在class
這個東西的。如果想要產生class,必須要用function搭配new
這個關鍵字使用(等等會講解)。
例如:
function Apple(){
let AppleContainer = document.createElement('div');
AppleContainer.textContent="蘋果";
return AppleContainer;
}
let AppleInstance1 = new Apple();
let AppleInstance2 = new Apple();
這個時候 AppleInstance1
和AppleInstance2
就會都會是內文是「蘋果」的div。
在JS中,如果function沒有return值,則new
這個關鍵字可以用來創造「實體」。以人來比喻的話,我們會認為人類是一種「類別(class)」,小明和小華會是人類的「實體(Instance)」,雖然小明和小華都有人類的特性,但這兩個人都是獨立的個體。同樣的,Apple是一種「類別(class)」,AppleInstance1
和AppleInstance2
則是Apple的「實體(Instance)」,他們都有Apple的特性,但這兩個都是獨立的個體。
在目前我們的範例中,因為有return一個DOM元素,所以效果和一般的函式差不多,不太算是Apple的物件實體,我們會在下一篇說明如何真正使用JS new出實體。
於是我們照著上面的方法,開始拆分結構,並把工廠模式加入其中,就變成了這樣:
function Menu(menuItemWording){
let menuContainer = document.createElement('div');
menuContainer.setAttribute('class',"menu-container");
//藍色標題
let title = document.createElement('p')
title.setAttribute('class',"menu-title")
title.textContent="Andy Chang的Like";
menuContainer.appendChild(title);
//列表的container
let menu = document.createElement('ul');
menu.setAttribute('class',"menu")
menuItemWording.forEach((item)=>{
let menuItem = document.createElement('li');
menuItem.setAttribute('class',"menu-item");
menuItem.textContent = item;
menu.appendChild(menuItem);
});
//控制「列表的container」開關的按鈕
let menuBtn = document.createElement('button');
let isOpen = false;
menuBtn.setAttribute('class',"menu-btn");
menuBtn.textContent="V";
menuBtn.onclick = function() {
// 「!」會把true變false,false變true
isOpen = !isOpen;
if(isOpen){
menu.style.display = "block";
menuBtn.textContent="^";
}
else{
menu.style.display = "none";
menuBtn.textContent="V";
}
}
menuContainer.appendChild(menuBtn);
menuContainer.appendChild(menu);
return menuContainer;
}
</body>
<script src="./js/component/menu.js" type="text/javascript"></script>
<script src="./js/index.js" type="text/javascript"></script>
</html>
// 文字
let menuItemWording=[
"Like的發問",
"Like的回答",
"Like的文章",
"Like的留言"
];
const MenuInstance = new Menu(menuItemWording);
document.getElementById('root').appendChild(MenuInstance);
如果class只能做這樣,那其實跟一般的函式沒甚麼差別。我們下一篇會繼續來談如何運用JS在ES5前的class讓程式更直覺。